package net;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;

import model.contents.Content;
import util.IModel;
import util.IView;
import util.LoggingUtils;

public class HTTPDownloader implements Downloader, IModel {

	private static HTTPDownloader _instance;

	private boolean override; // whether downloader should
								// override the local
								// file if the same file needs
								// to be downloaded.

	// the views listening to this model
	private List<IView> views;

	@Override
	public synchronized boolean download(String remotePath, String localPath) {
		URL url = null;
		BufferedInputStream bis = null;
		boolean saved = false;
		
		final String classMethod = "HTTPDownloader.java ownload(String remotePath, String localPath)";
		LoggingUtils.logStart(classMethod);
		
		try {
			url = new URL(remotePath);
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();

			conn.setConnectTimeout(50000);
			conn.setReadTimeout(600000);

			int contentLength = conn.getContentLength();

			if (contentLength <= 0) {
				System.out.println("Invalid Content Length for file: " + remotePath);
				return false;
			} else if (!this.isOverride()) {

				File localFile = new File(localPath);
				if (localFile.exists() && localFile.length() == contentLength && localFile.lastModified() >= conn.getLastModified()) {
					// local file is the same copy as the server file
					// file is already downloaded
					return true;
				}
			}

			if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
				System.out.println("Downloading file: " + remotePath + ", " + conn.getResponseCode() + ": " + conn.getResponseMessage());
				return false;
			}
			//System.out.println("Downloading file: " + remotePath);
			// read data from stream

			byte[] data = new byte[contentLength];
			try {

				synchronized (this.views) {
					for (IView view : this.views) {
						view.updateView("show", localPath);
					}
				}

				bis = new BufferedInputStream(conn.getInputStream());
				int bytesRead = 0;
				int offset = 0;
				int acc = 0;

				while (offset < contentLength) {
					bytesRead = bis.read(data, offset, contentLength - offset);
					// System.out.format("length: %d, read: %d, offset: %d\n",
					// contentLength, bytesRead, offset);
					if (bytesRead == -1) {
						break; // nothing to do
					}
					offset += bytesRead;
					acc += bytesRead;

					if (acc >= 1024 * 10) {
						synchronized (this.views) {
							for (IView view : this.views) {
								view.updateView(new Integer(offset), new Integer(contentLength));
							}
						}
						acc = 0;
					}
				}
			}
			catch(Exception e)
			{
				LoggingUtils.log(Level.SEVERE, e.getMessage() + " for the given url: " + remotePath + classMethod,e);
			}
			finally {
				synchronized (this.views) {
					for (IView view : this.views) {
						view.updateView("hide", null);
					}
				}
				bis.close();
			}

			// data transfer is complete, put data into file
			saved = this.writeDataToFile(data, localPath);

		}
		catch(UnknownHostException e)
		{
			LoggingUtils.log(Level.SEVERE, e.getMessage() + " for the given url: " + remotePath + classMethod,e);
		}
		catch(ConnectException e)
		{
			LoggingUtils.log(Level.SEVERE, e.getMessage() + " for the given url: " + remotePath + classMethod,e);
		}
		catch(SocketTimeoutException e)
		{
			LoggingUtils.log(Level.SEVERE, e.getMessage() + " for the given url: " + remotePath + classMethod,e);
		}
		catch (MalformedURLException e)
		{
			LoggingUtils.log(Level.SEVERE, e.getMessage() + " for the given url: " + remotePath + classMethod,e);
		}
		catch (IOException e) 
		{
			LoggingUtils.log(Level.SEVERE, e.getMessage() + " while opening connection for: " + remotePath + classMethod,e);
		}
		catch (Exception e) 
		{
			LoggingUtils.log(Level.SEVERE, e.getMessage() + " while opening connection for: " + remotePath + classMethod,e);
		}
		return saved;
	}

	// write bytes into file
	private boolean writeDataToFile(byte[] data, String fileName) {
		
		String classMethod = "HTTPDownloader.java writeDataToFile()";
		LoggingUtils.logStart(classMethod);
		
		BufferedOutputStream bos = null;
		File outputFile = new File(fileName);
		if (!outputFile.exists()) {
			// create directory for storing the file
			if (outputFile.getParentFile() != null) {
				outputFile.getParentFile().mkdirs();
				if (!outputFile.getParentFile().exists() || !outputFile.getParentFile().isDirectory()) {
					return false;
				}
			}
		}

		boolean saved = false;
		try {
			bos = new BufferedOutputStream(new FileOutputStream(outputFile));
			try {
				bos.write(data);
				saved = true;
			} finally {
				bos.close();
			}
		} catch (FileNotFoundException e) {
			// If the file exists but is a directory rather than a regular file,
			// does not exist but cannot be created, or cannot be opened for any
			// other reason then a FileNotFoundException is thrown.
			LoggingUtils.log(Level.SEVERE, e.getMessage() + classMethod,e);
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			LoggingUtils.log(Level.SEVERE, e.getMessage() + classMethod,e);
			e.printStackTrace();
		}
		catch(Exception e)
		{
			LoggingUtils.log(Level.SEVERE, e.getMessage() + classMethod,e);
		}

		File downloadedFile = new File(fileName);
		if (downloadedFile.exists() && downloadedFile.length() != data.length) {
			System.out.println("Data did not get saved propertly, deleting the file created: " + fileName);
			System.out.format("Expected file size: %d bytes, actual size: %d bytes.\n", data.length, downloadedFile.length());
			saved = false;
			downloadedFile.delete();
		}
		return saved;
	}

	public synchronized static HTTPDownloader getInstance() {
		if (_instance == null) {
			_instance = new HTTPDownloader();
		}
		return _instance;
	}

	private HTTPDownloader() {
		HttpURLConnection.setFollowRedirects(false);
		this.views = new ArrayList<IView>();
	}

	@Override
	public void setOverride(boolean overrideExistingFiles) {
		this.override = overrideExistingFiles;
	}

	@Override
	public boolean isOverride() {
		return this.override;
	}

	@Override
	public void addView(IView view) {
		synchronized (this.views) {
			if (!this.views.contains(view)) {
				this.views.add(view);
			}
		}
	}

	@Override
	public void removeView(IView view) {
		synchronized (this.views) {
			if (this.views.contains(view)) {
				this.views.remove(view);
			}
		}
	}

	@Override
	public void removeAllViews() {
		synchronized (this.views) {
			if (this.views == null) {
				this.views = new ArrayList<IView>();
			}
			
			try
			{
				this.views.clear();
			}
			catch(Exception e)
			{
				System.out.println("Exception in clearing views from HTTPDownloader.java removeallviews()");
				LoggingUtils.log(Level.SEVERE, e.getMessage(),e);
			}
		}
	}

	@Override
	public void notifyViews() {
		synchronized (this.views) {
			for (IView view : this.views) {
				view.updateView(this, null);
			}
		}
	}

	@Override
	public void addDownload(Content c) {
		// TODO Auto-generated method stub

	}
}
